package aspectjutil.eventmanager;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.aspectj.lang.Aspects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aspectjutil.adviseutil.Util;
import aspectjutil.eventmanager.EventInfo.EventerInfo;
import aspectjutil.eventmanager.label.EventTrigger;
import aspectjutil.eventmanager.label.OnEvent;
import aspectjutil.eventmanager.label.PreEvent;

/**
 * Based on {@link http://www.aspectprogrammer.org/blogs/adrian/2005/03/event_driven_ar.html}.
 * @author ay
 */
@Aspect
public class EventHandler {
	protected Logger logger = LoggerFactory.getLogger( this.getClass());
	
	/* Sort order at onEventMethodMap:
	 * 	1. event (done by key)
	 * 	2. turn in SubscriberMethodUnit
	 * 	3. staticMethod in SubscriberMethodUnit
	 */
	protected static Map<Class<?>, List<SubscriberMethodUnit>> onEventMethodMap 
	= Collections.synchronizedMap( new HashMap<Class<?>, List<SubscriberMethodUnit>>());
		/* LinkedList<SubscriberMethodUnit> for each event class in onEventMethodMap should not 
		 * contain duplicate SubscriberMethodUnit.
		 */
	protected static Map<Class<?>, List<SubscriberMethodUnit>> preEventMethodMap 
	= Collections.synchronizedMap( new HashMap<Class<?>, List<SubscriberMethodUnit>>());
	
	/**
	 * 
	 * @param method - to remove from list of event listening methods.
	 * @param methodClass - Class of instance object (does not mean to be method declaring class).
	 * @param event - listened by method.
	 */
	public static void removeMethodFromSubscription( Method method, Class<?> methodClass, Class<?> event) {
		if ( event != null) {
			synchronized( onEventMethodMap) {
				List<SubscriberMethodUnit> subscriberMethodUnitList = onEventMethodMap.get( event);
				
				for( 
						Iterator<SubscriberMethodUnit> subscriberMethodUnitIterator 
						= subscriberMethodUnitList.iterator();
						subscriberMethodUnitIterator.hasNext();
						) 
				{
					SubscriberMethodUnit subscriberMethodUnit = subscriberMethodUnitIterator.next();
					if ( method.equals( subscriberMethodUnit.getMethod())) {
						if ( methodClass != null) {
							if ( methodClass.equals( subscriberMethodUnit.getMethodClass())) {
								subscriberMethodUnitIterator.remove();
								break;
							}
						}
						else {
							subscriberMethodUnitIterator.remove();
						}
					}
				} // for
				onEventMethodMap.notifyAll();
			} // synchronized
		}
	}
	
	/**
	 * @param method - to remove from list of event listening methods.
	 * @param methodClass - Class of instance object (does not mean to be method declaring class).
	 */
	public static void removeMethodFromSubscription( Method method, Class<?> methodClass) {
		synchronized( onEventMethodMap) {
			for( Class<?> event : onEventMethodMap.keySet()) {
				removeMethodFromSubscription( method, methodClass, event);
			} // for
			onEventMethodMap.notifyAll();
		} // synchronized
	}
	
//TODO add method to change turn
//TODO add method to get turn
//TODO add contains method to check if method is contained in onEventMethodMap
//TODO add add method to add a method to onEventMethodMap
	
	
	public static boolean isOverriddenMethod( Method descendantMethod, Method ancestorMethod) {
		if ( 
				!ancestorMethod.getName()
				.equals( descendantMethod.getName())
				) return false;
		if ( 
				!Arrays.equals( 
						ancestorMethod.getParameterTypes(), descendantMethod.getParameterTypes())
				) return false;
		if ( 
				!ancestorMethod.getGenericReturnType()
				.equals( descendantMethod.getGenericReturnType())
				) return false;
		
		return true;
	} // public static boolean isOverriddenMethod( Method descendantMethod, Method ancestorMethod)
	
	/**
	 * @param method
	 * @param descendantMethodSet: Set of methods being searched overriden one for method input. 
	 * @return true when descendantMethodSet contains method of same signature with method input.
	 */
	public static boolean isOverriddenMethod( Method method, Set<Method> descendantMethodSet) {
		// Check whehter method has been overridden in descendant class
		String methodName = method.getName();
		Class<?>[] parameterTypes = method.getParameterTypes();
		Type methodReturnType = method.getGenericReturnType();
		boolean overrideFlag = false;
		synchronized( descendantMethodSet) {
			for( Method descendantMethod : descendantMethodSet) {
				if ( !methodName.equals( descendantMethod.getName())) continue;
				if ( !Arrays.equals( parameterTypes, descendantMethod.getParameterTypes())) continue;
				if ( !methodReturnType.equals( descendantMethod.getGenericReturnType())) continue;
				
				overrideFlag = true;
				break;
			} // for
			descendantMethodSet.notifyAll();
		} // synchronized
		
		return overrideFlag;
	} // public static boolean isOverriddenMethod( Method method, Set<Method> descendantMethodSet)
	
	/**
	 * @param classObj: Class to search method. (method search including methods in ancestor classes.)
	 * @param nonOverridenDescendantMethodSet: not-static methods excluding inherited ones on the line of the lineage of classObj will be stored.
	 * @param staticMethodSet: all static methods on the line of the lineage of classObj will be stored.
	 */
//TODO Extract this method into other appropriate class out of this class
	public static void buildMethodSet( 
			Class<?> classObj, Set<Method> nonOverridenDescendantMethodSet, Set<Method> staticMethodSet) {
		synchronized( nonOverridenDescendantMethodSet) {
			for( Method method : classObj.getDeclaredMethods()) {
				if ( Modifier.isAbstract( method.getModifiers())) {
					continue; // for 
				}
				if ( Modifier.isStatic( method.getModifiers())) {
					staticMethodSet.add( method);
				}
				else if ( !isOverriddenMethod( method, nonOverridenDescendantMethodSet)) {
					nonOverridenDescendantMethodSet.add( method);
				}
			} // for
			nonOverridenDescendantMethodSet.notifyAll();
		} // synchronized
		
		Class<?> superClass = classObj.getSuperclass();
		if ( superClass != null) {
			buildMethodSet( superClass, nonOverridenDescendantMethodSet, staticMethodSet);
		}
	} // public static void buildNonOverridenMethodSet( Class<?> classObj, Set<Method> nonOverridenMethodSet)
	
	protected static SubscriberMethodUnitComparator subscriberMethodUnitComparator
	= new SubscriberMethodUnitComparator();
	
	protected void addMethodToEventSubscriberMethodMap( 
			Map<Class<?>, List<SubscriberMethodUnit>> subscriberMethodMap,
			Method method, Class<?> methodOwner, 
			Class<?>[] eventClassArray, boolean amongInstancesInThread, int[] turnOnEachEventArray) 
	{
		for( int eventIndex = 0; eventIndex < eventClassArray.length; eventIndex++) {
			SubscriberMethodUnit subscriberMethodUnit;
			if ( eventIndex < turnOnEachEventArray.length) {
				subscriberMethodUnit
				= new SubscriberMethodUnit( 
						method, 
						methodOwner,
						amongInstancesInThread, 
						turnOnEachEventArray[ eventIndex]
						);
			}
			else {
				subscriberMethodUnit
				= new SubscriberMethodUnit( 
						method, 
						methodOwner, 
						amongInstancesInThread, 
						SubscriberMethodUnit.DEFAULT_TURN_VALUE
						);
			}
			
			synchronized( subscriberMethodMap) {
				List<SubscriberMethodUnit> subscriberMethodUnitList 
				= subscriberMethodMap.get( eventClassArray[ eventIndex]);
				if ( subscriberMethodUnitList == null) {
					subscriberMethodUnitList 
					= Collections.synchronizedList( new LinkedList<SubscriberMethodUnit>());
					subscriberMethodMap.put( eventClassArray[ eventIndex], subscriberMethodUnitList);
				}
				
				if ( !subscriberMethodUnitList.contains( subscriberMethodUnit)) {
					if ( subscriberMethodUnitList.add( subscriberMethodUnit)) {
						Collections.sort( subscriberMethodUnitList, subscriberMethodUnitComparator);
					}
					else {
						if ( logger.isWarnEnabled()) {
							logger.warn(
									String.format(
											
											"Failed to add %1$s method to list of event listener methods " 
											+ "annotated with %2$s.%nPlease report this incidence.",
											method.toGenericString().replace( 
													method.getDeclaringClass().getName(), 
													methodOwner.getName()
													), 
											(subscriberMethodMap.equals( onEventMethodMap) 
													? "@OnEvent" : "@PreEvent")
											)
									);
						}
					}
				}
				else {
					if ( !Modifier.isStatic( method.getModifiers()) && logger.isWarnEnabled()) {
						logger.warn(
								String.format(
										"Detected the attempt of adding duplication of %1$s method to list " 
										+ "of event listener methods annotated with %2$s.%nThis may be " 
										+ "caused by multiple class loaders if it has not been intentionally " 
										+ "added before.",
										method.toGenericString().replace( 
												method.getDeclaringClass().getName(), 
												methodOwner.getName()
												), 
										(subscriberMethodMap.equals( onEventMethodMap) 
												? "@OnEvent" : "@PreEvent")
										)
								);
					}
				}
				subscriberMethodMap.notifyAll();
			} // synchronized
		} // for
	} // protected void addMethodToEventSubscriberMethodMap(.....)
	
	protected void addMethodToOnEventMethodMap( Method method, Class<?> methodOwner, OnEvent onEvent) {
		Class<?>[] eventClassArray = onEvent.value();
		boolean amongInstancesInThread = onEvent.amongInstancesInThread();
		int[] turnOnEachEventArray = onEvent.turnOnEachEvent();
		
		addMethodToEventSubscriberMethodMap( 
				onEventMethodMap, method, methodOwner, eventClassArray, amongInstancesInThread, turnOnEachEventArray);
	}
	
	protected void addMethodToPreEventMethodMap( Method method, Class<?> methodOwner, PreEvent preEvent) {
		Class<?>[] eventClassArray = preEvent.value();
		boolean amongInstancesInThread = preEvent.amongInstancesInThread();
		int[] turnOnEachEventArray = preEvent.turnOnEachEvent();

		addMethodToEventSubscriberMethodMap( 
				preEventMethodMap, method, methodOwner, eventClassArray, amongInstancesInThread, turnOnEachEventArray);
	}
	
	/**
	 * Scan through class hierarchy for methods annotated with @onEvent and build up 
	 * onEventMethodMap with information of events and event listener methods. 
	 * @param classObj
	 * @param onEventMethodSet: To store methods annotated with @onEvent or both of @onEvent and @Override 
	 * including methods in super class.
	 * @param overriddenMethodSet: To store methods annotated only with @Override 
	 * including methods in super class.
	 */
	protected void huntListenerMethods( 
			Class<?> subjectClass, Class<?> classObj, Set<Method> nonOverridenDescendantMethodSet) 
	{
		for( Method method : classObj.getDeclaredMethods()) {
			int methodModifiers = method.getModifiers();
			if ( Modifier.isAbstract( methodModifiers)) {
				continue; // for
			}
			
			OnEvent onEvent = method.getAnnotation( OnEvent.class);
			PreEvent preEvent = method.getAnnotation( PreEvent.class); 
			
			// validate @OnEvent method and @PreEvent method 
			Class<?>[] parameterTypes = method.getParameterTypes();
			if ( ( onEvent != null) || ( preEvent != null)) { 
				// Check on argument of method; should have only EventInfo type argument. 
				if ( parameterTypes.length != 1 || !EventInfo.class.isAssignableFrom( parameterTypes[ 0])) {
					throw new RuntimeException( 
							String.format(
									"Bad method signature at %1$s%n" 
									+ "Method annotated with either @OnEvent or @PreEvent is allowed to " 
									+ "have only EventInfo parameter.", 
									method.toString() 
									)
							);
				}
			}
			
			if ( Modifier.isStatic( method.getModifiers())) {
				// Check whether method is annotated with @OnEvent 
				if ( onEvent != null) {
					/* All static method should be added onEventMethodMap.
					 * Providing classObj since class defining method is matter for static method case 
					 */
					addMethodToOnEventMethodMap( method, classObj, onEvent);
					if ( logger.isDebugEnabled()) {
						logger.debug(
								String.format( 
										"added %1$s static method to list of methods annotated with " 
										+ "@OnEvent for %2$s event(s)", 
										method.toGenericString(),
										Arrays.toString( onEvent.value())
										)
								);
					}
				}
				if ( preEvent != null) {
					/* All static method should be added preEventMethodMap.
					 * Providing classObj since class defining method is matter for static method case 
					 */
					addMethodToPreEventMethodMap( method, classObj, preEvent);
					if ( logger.isDebugEnabled()) {
						logger.debug(
								String.format( 
										"added %1$s static method to list of methods annotated with " 
										+ "@PreEvent for %2$s event(s)", 
										method.toGenericString(),
										Arrays.toString( preEvent.value())
										)
								);
					}
				}
			}
			else if ( !isOverriddenMethod( method, nonOverridenDescendantMethodSet)) { // method has not been overridden by subclass yet
				nonOverridenDescendantMethodSet.add( method);
				
				// Check whether method is annotated with @OnEvent 
				if ( onEvent != null) {
					/* Add non-static method excluding inherited ones to onEventMethodMap.
					 * Providing subjectClass since instance object is matter for non-static method case
					 */
					addMethodToOnEventMethodMap( method, subjectClass, onEvent);
					if ( logger.isDebugEnabled()) {
						logger.debug(
								String.format( 
										"added %1$s method to list of methods annotated with " 
										+ "@OnEvent for %2$s event(s)", 
										method.toGenericString().replace( 
												method.getDeclaringClass().getName(), 
												subjectClass.getName()
												),
										Arrays.toString( onEvent.value())
										)
								);
					}
				}
				// Check whether method is annotated with @PreEvent 
				if ( preEvent != null) {
					/* Add non-static method excluding inherited ones to preEventMethodMap.
					 * Providing subjectClass since instance object is matter for non-static method case
					 */
					addMethodToPreEventMethodMap( method, subjectClass, preEvent);
					if ( logger.isDebugEnabled()) {
						logger.debug(
								String.format( 
										"added %1$s method to list of methods annotated with " 
										+ "@PreEvent for %2$s event(s)", 
										method.toGenericString().replace( 
												method.getDeclaringClass().getName(), 
												subjectClass.getName()
												),
										Arrays.toString( preEvent.value())
										)
								);
					}
				}
			}
		} // for
		
		Class<?> superClass = classObj.getSuperclass();
		if ( superClass != Object.class) {
			huntListenerMethods( subjectClass, superClass, nonOverridenDescendantMethodSet);
		}
	} // protected void huntListenerMethods( Class<?> subjectClass, Class<?> classObj, Set<Method> nonOverridenDescendantMethodSet)
	
	@Pointcut( "staticinitialization( @aspectjutil.eventmanager.label.EventListener *)")
	public static void eventListenerStaticinitializationPointcut() {}
	@Before( value="eventListenerStaticinitializationPointcut()")
	public void beforeEventListenerStaticinitialization( JoinPoint joinPoint) {
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"%1$s has been picked up (by AspectJ's staticinitialization) as annotated with @EventListener",
							joinPoint.getSignature().toString()
							)
					);
		}

		Class<?> eventListenerClass = joinPoint.getSignature().getDeclaringType();
		if ( eventListenerClass.isInterface()) {
			throw new RuntimeException(
						String.format(
								"@EventListener annotation should be applied only to class but " 
								+ "%1$s is interface.",
								eventListenerClass.toString()
								)
					);
		}
	}
	
	@AfterReturning( pointcut="eventListenerStaticinitializationPointcut()")
	public void afterEventListenerStaticinitialization( JoinPoint joinPoint) {
		Class<?> eventListenerClass = joinPoint.getSignature().getDeclaringType();
		Set<Method> nonOverridenDescendantMethodSet = new LinkedHashSet<Method>();
		huntListenerMethods( 
				eventListenerClass, eventListenerClass, nonOverridenDescendantMethodSet);
	}
	
	
	protected Method getMethod( Class<?> classObj, String methodName, Class<?>[] parameterTypes) {
		Method method = null;
		try {
			method = classObj.getDeclaredMethod( methodName, parameterTypes);
		}
		catch( NoSuchMethodException exception) {
			/* Do nothing */
		}
		catch( Throwable throwable) {
/*			if ( logger.isWarnEnabled()) {
				logger.warn( 
						String.format(
								"Failed in attempting to obtain %1$s method in %2 class by reflection.", 
								methodName, classObj.getName()
								), 
						throwable
						);
			}
			return null;
*/
			throw new RuntimeException( 
					String.format(
							"Failed in attempting to obtain %1$s method in %2 class by reflection.", 
							methodName, classObj.getName()
							), 
					throwable
					);
		}
		
		if ( method == null) {
			Class<?> superClass = classObj.getSuperclass();
			if ( superClass == null) return null;
			
			method = getMethod( superClass, methodName, parameterTypes);
		}
			
		return method;
	} // protected Method getMethod( Class<?> classObj, String methodName, Class<?>[] parameterTypes)
	
	protected List<SubscriberMethodUnit> getSubscriberMethodUnitListForEvent( 
			Map<Class<?>, List<SubscriberMethodUnit>> subscriberMethodMap, Class<?> event) {
		
		List<SubscriberMethodUnit> subscriberMethodUnitList = null;
		synchronized( subscriberMethodMap) {
			List<SubscriberMethodUnit> subscriberMethodUnitOriginalList = subscriberMethodMap.get( event);
			if ( subscriberMethodUnitOriginalList == null) {
				if ( logger.isDebugEnabled()) {
					logger.debug(
							String.format(
									"No %1$s type listener for %2$s event",
									(subscriberMethodMap.equals( onEventMethodMap) ? "@OnEvent" : "@PreEvent"),
									event.getName()
									)
							);
				}
			}
			else {
				subscriberMethodUnitList 
				= new LinkedList<SubscriberMethodUnit>( subscriberMethodUnitOriginalList);
			}
			subscriberMethodMap.notifyAll();
		} // synchronized
		
		return subscriberMethodUnitList;
	} // protected List<SubscriberMethodUnit> getSubscriberMethodUnitListForEvent(..)
	
	protected void publishEvent( 
			Map<Class<?>, List<SubscriberMethodUnit>> subscriberMethodMap, EventInfo eventInfo) {
		Class<?> event = eventInfo.getTriggeredEvent();
		
		List<SubscriberMethodUnit> subscriberMethodUnitList 
		= getSubscriberMethodUnitListForEvent( subscriberMethodMap, event);
		if ( subscriberMethodUnitList == null) return;
		
		StringBuffer stringBuffer = new StringBuffer();
		for( SubscriberMethodUnit subscriberMethodUnit : subscriberMethodUnitList) {
			Method listeningMethod = subscriberMethodUnit.getMethod();
			listeningMethod.setAccessible( true);
			Class<?> methodClass = subscriberMethodUnit.getMethodClass();
			
			if ( Modifier.isStatic( listeningMethod.getModifiers())) {
				if ( subscriberMethodUnit.isAmongInstancesInThread()) {
					String methodName = methodClass.getName() + "." + listeningMethod.getName();
					if ( logger.isInfoEnabled()) {
						logger.info( 
								String.format(
										"amongInstancesInThread attribute setting of %1$s annotation is ignored " 
										+ "as %2$s is static method.",
										(onEventMethodMap.equals( subscriberMethodMap) ? "@OnEvent" : "@PreEvent"),
										methodName
										)
								);
					}
				}
				
				try {
					listeningMethod.invoke( null, eventInfo);
					
					if ( logger.isDebugEnabled()) {
						String methodName = methodClass.getName() + "." + listeningMethod.getName();
						logger.debug(
								String.format( 
										"Invoked %1$s event-listener static method (annotated " 
										+ "with %2$s) %3$s %4$s event", 
										methodName, 
										(onEventMethodMap.equals( subscriberMethodMap) ? "@OnEvent" : "@PreEvent"),
										(onEventMethodMap.equals( subscriberMethodMap) ? "at" : "before"),
										event.getName()
										)
								);
					}
				}
				catch( Throwable throwable) {
					synchronized( subscriberMethodMap) {
						List<SubscriberMethodUnit> subscriberMethodUnitOriginalList 
						= subscriberMethodMap.get( event);
						if ( subscriberMethodUnitOriginalList != null) {
							subscriberMethodUnitOriginalList.remove( subscriberMethodUnit);
						}
						subscriberMethodMap.notifyAll();
					} // synchronized
					
					String methodName = methodClass.getName() + "." + listeningMethod.getName();
					stringBuffer.append( "\n" + methodName);
					if ( logger.isErrorEnabled()) {
						logger.error(
								String.format(
										"%1$s event listener static method (annotated with %2$s) won't be " 
										+ "executed any longer %3$s further %4$s event due to this experience " 
										+ "of failure in its execution.",
										methodName,
										(onEventMethodMap.equals( subscriberMethodMap) ? "@OnEvent" : "@PreEvent"),
										(onEventMethodMap.equals( subscriberMethodMap) ? "at" : "before"),
										event.getName()
										),
								throwable 
								);
					}
				}
			}
			else {
				Map<Object, Long> eventListerInstanceMap 
				= Aspects.aspectOf( EventListenerInstanceTracker.class, methodClass)
					.getListeners( methodClass);
				long threadId = Thread.currentThread().getId();
				for( Entry<Object, Long> eventListerInstanceEntry : eventListerInstanceMap.entrySet()) {
					// eventListerInstanceEntry consists of event listener instance (object) and its thread id (long)
					
					if ( subscriberMethodUnit.isAmongInstancesInThread()) {
						if ( threadId != eventListerInstanceEntry.getValue().longValue()) continue; // for
					}
					
					try {
						listeningMethod.invoke( eventListerInstanceEntry.getKey(), eventInfo);
						
						if ( logger.isDebugEnabled()) {
							String methodName = methodClass.getName() + "." + listeningMethod.getName();
							logger.debug(
									String.format(
											"Invoked %1$s event-listener method (annotated " 
											+ "with %2$s) of %3$s instance %4$s %5$s event", 
											methodName, 
											(onEventMethodMap.equals( subscriberMethodMap) ? "@OnEvent" : "@PreEvent"),
											eventListerInstanceEntry.getKey().toString(),
											(onEventMethodMap.equals( subscriberMethodMap) ? "at" : "before"),
											event.getName()
											)
									);
						}
					}
					catch( Throwable throwable) {
						synchronized( subscriberMethodMap) {
							List<SubscriberMethodUnit> subscriberMethodUnitOriginalList 
							= subscriberMethodMap.get( event);
							if ( subscriberMethodUnitOriginalList != null) {
								subscriberMethodUnitOriginalList.remove( subscriberMethodUnit);
							}
							subscriberMethodMap.notifyAll();
						} // synchronized
						
						String methodName = methodClass.getName() + "." + listeningMethod.getName();
						stringBuffer.append( "\n" + methodName);
						if ( logger.isErrorEnabled()) {
							logger.error(
									String.format(
											"%1$s event listener method (annotated with %2$s) won't be " 
											+ "executed any longer %3$s further %4$s event due to this " 
											+ "experience of failure in its execution.",
											methodName,
											(onEventMethodMap.equals( subscriberMethodMap) ? "@OnEvent" : "@PreEvent"),
											(onEventMethodMap.equals( subscriberMethodMap) ? "at" : "before"),
											event.getName()
											),
									throwable 
									);
						}
					}
				} // for
			}
		} // for
		if ( stringBuffer.length() > 0) {
			throw new RuntimeException( 
					String.format(
							"Any further %1$s event won't be published to the event listener " 
							+ "method(s) below due to previous occurance of failure(s). Please check error " 
							+ "logs for each failure. %2$s",
							event.getName(), 
							stringBuffer.toString()
							) 
					);
		}
	} // protected void publishEvent( EventInfo eventInfo)
	
	
	@Pointcut( 
			"execution( @aspectjutil.eventmanager.label.EventTrigger * *(..)) " +
			"&& @annotation( eventTrigger)"
			)
	public static void executingEventTriggerPointcut( EventTrigger eventTrigger) {}
	
	@Before( value="executingEventTriggerPointcut( eventTrigger)", argNames="eventTrigger")
	public void beforeExecutingEventTrigger( JoinPoint joinPoint, EventTrigger eventTrigger) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @PreEvent type event listener method(s) for %1$s event " +
							"prior to execution of method below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = ((CodeSignature)joinPoint.getSignature()).getDeclaringType();
		String methodName = ((CodeSignature)joinPoint.getSignature()).getName();
		Class<?>[] parameterTypes = ((CodeSignature)joinPoint.getSignature()).getParameterTypes();
		
		Method method = getMethod( declaringType, methodName, parameterTypes);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, method);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, joinPoint.getArgs(), null);
		
		publishEvent( preEventMethodMap, eventInfo);
	}
	
	@AfterReturning( 
			pointcut="executingEventTriggerPointcut( eventTrigger)", 
			returning="returnedObj",
			argNames="eventTrigger,returnedObj"
			)
	public void afterExecutingEventTrigger( 
			JoinPoint joinPoint, EventTrigger eventTrigger, Object returnedObj) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @OnEvent type event listener method(s) for %1$s event " +
							"triggered from execution of method below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = ((CodeSignature)joinPoint.getSignature()).getDeclaringType();
		String methodName = ((CodeSignature)joinPoint.getSignature()).getName();
		Class<?>[] parameterTypes = ((CodeSignature)joinPoint.getSignature()).getParameterTypes();
		
		Method method = getMethod( declaringType, methodName, parameterTypes);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, method);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, joinPoint.getArgs(), returnedObj);
		
		publishEvent( onEventMethodMap, eventInfo);
	}
	
	
	protected Field getField( Class<?> classObj, String fieldName) {
		Field field = null;
		try {
			field = classObj.getDeclaredField( fieldName);
		}
		catch( NoSuchFieldException exception) {
			/* Do nothing */
		}
		catch( Throwable throwable) {
/*			if ( logger.isWarnEnabled()) {
				logger.warn( 
						String.format(
								"Failed in attempting to obtain %1$s field in %2 class by reflection.", 
								fieldName, classObj.getName()
								), 
						throwable
						);
			}
			return null;
*/
			throw new RuntimeException( 
					String.format(
							"Failed in attempting to obtain %1$s field in %2 class by reflection.", 
							fieldName, classObj.getName()
							), 
					throwable
					);
		}
		
		if ( field == null) {
			Class<?> superClass = classObj.getSuperclass();
			if ( superClass == null) return null;
			
			field = getField( superClass, fieldName);
		}
			
		return field;
	} // protected Method getField( Class<?> classObj, String fieldName)
	
	@Pointcut( 
			"set( @aspectjutil.eventmanager.label.EventTrigger * *) " 
			+ "&& @annotation( eventTrigger) && args( fieldValue)"
			)
	public static void eventTriggerSetterPointcut( EventTrigger eventTrigger, Object fieldValue) {}
	
	@Before( value="eventTriggerSetterPointcut( eventTrigger, fieldValue)")
	public void beforeEventTriggerSetter( JoinPoint joinPoint, EventTrigger eventTrigger, Object fieldValue) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @PreEvent type event listener method(s) for %1$s event " +
							"prior to setting value to the field below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = joinPoint.getSignature().getDeclaringType();
		String fieldName = joinPoint.getSignature().getName();
		
		Field field = getField( declaringType, fieldName);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, field);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, null, fieldValue);
		
		publishEvent( preEventMethodMap, eventInfo);
	}
	
	@AfterReturning( 
			pointcut="eventTriggerSetterPointcut( eventTrigger, fieldValue)", 
			argNames="eventTrigger, fieldValue"
			)
	public void afterEventTriggerSetter( JoinPoint joinPoint, EventTrigger eventTrigger, Object fieldValue) {
		Class<?> triggeredEvent = eventTrigger.value();
		if ( logger.isDebugEnabled()) {
			logger.debug(
					String.format(
							"Going to execute @OnEvent type event listener method(s) for %1$s event " +
							"triggered from setting value to the field below: %n%2$s",
							triggeredEvent.getName(), 
							Util.getJoinPointInfo( joinPoint, null)
							)
					);
		}
		
		Object targetObj = joinPoint.getTarget();
		Class<?> declaringType = joinPoint.getSignature().getDeclaringType();
		String fieldName = joinPoint.getSignature().getName();
		
		Field field = getField( declaringType, fieldName);
		
		EventerInfo eventerInfo = new EventerInfo( targetObj, field);
		EventInfo eventInfo = new EventInfo( triggeredEvent, eventerInfo, null, fieldValue);
		
		publishEvent( onEventMethodMap, eventInfo);
	} // public void afterEventTriggerSetter( JoinPoint joinPoint, EventTrigger eventTrigger, Object fieldValue)
	
//TODO Create the method like EventHandler.huntListenerMethods method for @EventTriger in order to support inheritance of @EventTriger. 
}
